#define _GNU_SOURCE

#include "task.h"
#include <sched.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>

#define PREV_FRAME(frame) (((frame) + NUM_FRAMES-1 ) % NUM_FRAMES)


#define TEMPO_BASE 10
#define T_TEST


typedef enum {IDLE, PENDING, RUNNING } t_state;
typedef enum { FALSE, TRUE } bool;

typedef struct
{
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    unsigned int id;
    unsigned int runcounter;
    t_state state;  //boooooooooooolean da 3tre3tre
    pthread_t thread;
}
attr;

bool* suspended;

void task_suspend(unsigned task_id)
{
  suspended[task_id]=TRUE;
  
}

void task_wakeup(unsigned task_id)
{
  suspended[task_id]=FALSE;
}


/* handler dei thread relativi ai task
*	1. mi metto IDLE
* 	2. mi metto in attesa sulla mia cond che mi verrà segnalata da executive..
* 	3. eseguo la mia funzione
* 	4. ripeto
*/
void* task_handler (void * arrg)
{
    attr *arg = (attr *) arrg;
//           printf("sono il job n° %d e mi sono messo IDLE\n", arg->id);
struct timeval acttime;
    
    while (1) {
        pthread_mutex_lock(&arg->mutex);
        arg->state = IDLE;

        pthread_cond_wait( &arg->cond,&arg->mutex);

        arg->state = RUNNING;
        arg->runcounter++;
        pthread_mutex_unlock(&arg->mutex);

	   #ifdef T_TEST           
    gettimeofday(&acttime,NULL);
	  unsigned int before = acttime.tv_usec*1000;
    #endif
	
        TASKS[arg->id](); //ESEGUI IL TUO TASKs IESIMO
    
       #ifdef T_TEST
    gettimeofday(&acttime,NULL);
	  unsigned int after = acttime.tv_usec*1000;
    printf("task n %d eseguito in  %dnsec\n", arg->id,after-before);	
    #endif
	
    }

    return NULL;
}


/* handler dell'executive
*	1. calcolo il mio frame attuale
* 	2. controllo deadline miss su frame precedente
* 	3. schedulo il frame attuale
* 	4. mi metto in attesa con timeout fino al prossimo frame
* 	5. ripeto
*/
void* executive_handler (void * arrg)
{
    attr *arg = (attr *) arrg;
    struct timespec time;
    struct timeval utime;
//   double actualFrame = 0;
    struct sched_param sched_par;
    int i,n,frame;

    gettimeofday(&utime,NULL);

    time.tv_sec = utime.tv_sec;
    time.tv_nsec = utime.tv_usec * 1000;

    unsigned int j=0;
    while (1)
    {
 
//printf("TIME(per lollo) = %d, %d",time.tv_sec,time.tv_nsec);
//     actualFrame = ((time.tv_sec - utime.tv_sec) * 1000000000 + (time.tv_nsec - utime.tv_usec * 1000) )
// 								    / 1000000 / TEMPO_BASE / (H_PERIOD / NUM_FRAMES) % NUM_FRAMES;
        frame=j; //serve per la definizione di PREV_FRAME in alto
//     frame= (int)(actualFrame + 0.5);
 
  //controllo deadline miss
	for (i=0;i<NUM_TASKS;i++)        {
            pthread_mutex_lock(&arg[i].mutex);
            if (arg[i].state != IDLE) {
		pthread_mutex_unlock(&arg[i].mutex);
                printf("\t_________________________________________________________________DEADLINE MISS DEL PROCESSO n° %d con state=%d e runcounter= %d nel frame n° %d\n\n\n", i, arg[i].state, arg[i].runcounter, frame);
	    	suspended[i]=TRUE;
		   sched_par.sched_priority = sched_get_priority_max( SCHED_FIFO )-i; 
		   pthread_setschedparam( arg[i].thread, SCHED_FIFO, &sched_par);

	    }
	    else pthread_mutex_unlock(&arg[i].mutex);
        }
       

	//controllo wakeup di deadline anche vecchie
	for (n=0; n<NUM_TASKS; n++) 
	{ 
	  pthread_mutex_lock(&arg[n].mutex);
	    if ( arg[n].state == IDLE) 
	    {
	      pthread_mutex_unlock(&arg[n].mutex);
	      task_wakeup(arg[n].id);
	    } 
	    else pthread_mutex_unlock(&arg[n].mutex);
	}

printf(" \n frame n° %d\nruncounter0=%d, state0=%d suspended0=%d\n runcounter1=%d, state1=%d suspended1=%d\n",frame, arg[0].runcounter, arg[0].state, suspended[0], arg[1].runcounter,arg[1].state, suspended[1]);


        //schedulazione per il frame attuale
        i =0;
        while (SCHEDULE[frame][i] != -1)
        {
           if (!suspended[SCHEDULE[frame][i]]) 
	   {
	      sched_par.sched_priority = sched_get_priority_max( SCHED_FIFO )-1-NUM_TASKS-i; //priorità in ordine decrescente a partire da quella dell'executive (max-1) lasciandone NUM_TASKS libere
            pthread_setschedparam( arg[SCHEDULE[frame][i]].thread, SCHED_FIFO, &sched_par);

            pthread_mutex_lock(&arg[SCHEDULE[frame][i]].mutex);
            arg[SCHEDULE[frame][i]].state = PENDING;
            pthread_mutex_unlock(&arg[SCHEDULE[frame][i]].mutex);
printf("gigio %d\n\n", SCHEDULE[frame][i]);
            pthread_cond_signal(&arg[SCHEDULE[frame][i]].cond);
	  }
          i++;
        }


        time.tv_sec += ( time.tv_nsec + H_PERIOD / NUM_FRAMES * TEMPO_BASE * 1000000) / 1000000000;
        time.tv_nsec = ( time.tv_nsec + H_PERIOD / NUM_FRAMES * TEMPO_BASE * 1000000 ) % 1000000000;

        pthread_cond_timedwait( &arg[NUM_TASKS].cond, &arg[NUM_TASKS].mutex, &time );

        j++;
        if (j==NUM_FRAMES) j=0;

//       j==NUM_FRAMES-1? j=0 : j++;
    }
    return NULL;
}


/* MAIN
*    1. inizializza il set di attributi per i thread e per l'executive -->(NUM_TASKS+1)
* 	2. setta la CPUZERO in modo da simulare un unico processore
* 	3. creo i thread a priorità decrescente da max-1
* 	4. mi metto in attesa con timeout fino al prossimo frame
* 	5. ripeto
*/
int main () {
    task_init();
    attr *attributi;  //NUM_TASKS+1 per l'executive
    attributi = (attr *) malloc( sizeof( attr ) * (NUM_TASKS +1));
    int i;
    suspended = (bool *) malloc( sizeof(bool) * NUM_TASKS);
    for (i = 0; i<NUM_TASKS+1;i++)
    {
        pthread_mutex_init( &attributi[i].mutex, NULL );
        pthread_cond_init( &attributi[i].cond, NULL );
        attributi[i].id =0;
        attributi[i].runcounter=0;
        attributi[i].state=IDLE;
        suspended[i]=FALSE;
    }

    //setta la cpu zero
    cpu_set_t set;
    CPU_ZERO(&set);
    CPU_SET(0, &set);
    sched_setaffinity(getpid(),sizeof(cpu_set_t), &set);

    pthread_attr_t th_attr;
    pthread_attr_init( &th_attr );
    pthread_attr_setschedpolicy( &th_attr, SCHED_FIFO );
    struct sched_param sch_param;
//setto la priorità a MAX-1
    sch_param.sched_priority = sched_get_priority_max( SCHED_FIFO ) - 1;

    //creo i thread per i tasks con priorità max-1
    for ( i = 0; i < NUM_TASKS; ++i )
    {
        attributi[i].id = i;
        pthread_attr_setschedparam( &th_attr, &sch_param );
#ifdef NPTL_WORKAROUND
        if ( pthread_create( &attributi[i].thread, &th_attr, task_handler, &attributi[i]) != 0 ||
                pthread_setschedparam( &attributi[i].thread, SCHED_FIFO, &sch_param ) != 0
           )
#else
        if ( pthread_create( &attributi[i].thread, &th_attr, task_handler, &attributi[i]) != 0 )
#endif
        {
            fprintf( stderr, "Error: Non enough privileges.\n" );
            exit(-1);
        }
        //--sch_param.sched_priority;  //non serve perchè li creo con priorità tutti uguale. sarà l'executive a assegnare le priorità
    }

    sleep(TEMPO_BASE*(H_PERIOD/NUM_FRAMES)/1000); //aspetto un frame che i thread si posizionino ai blocchi di partenza


//setto la priorità a MAX per l'executive
    sch_param.sched_priority = sched_get_priority_max( SCHED_FIFO );
    pthread_attr_setschedparam( &th_attr, &sch_param );
#ifdef NPTL_WORKAROUND
    if ( pthread_create( &attributi[NUM_TASKS].thread, &th_attr, executive_handler, attributi) != 0 ||
            pthread_setschedparam( &attributi[NUM_TASKS].thread, SCHED_FIFO, &sch_param ) != 0
       )
#else
    if ( pthread_create( &attributi[NUM_TASKS].thread, &th_attr, executive_handler, attributi) != 0 )
#endif
    {
        fprintf( stderr, "Error: Non enough privileges.\n" );
        exit(-1);
    }


//join dei thread
    for ( i = 0; i < NUM_TASKS+1; ++i )
        pthread_join( attributi[i].thread, NULL );

    return 0;
}
